package com.exam.api.common.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @Author bcheng
 * @Create 2024/7/5 上午11:28
 * @Description 通用工具类，用于处理列表相关的操作
 */
public class FindUtil extends ListDiffUtil {

    /**
     * 通用方法，用于在列表中查找符合条件的第一个对象
     *
     * @param list      要搜索的列表
     * @param predicate 判断条件
     * @param <T>       列表中对象的类型
     * @return 一个Optional对象，包含符合条件的第一个对象，如果未找到则为Optional.empty()
     */
    public static <T> Optional<T> findFirst(List<T> list, Predicate<T> predicate) {
        if (CollUtil.isEmpty(list)) {
            return Optional.empty();
        }
        return list.stream().filter(predicate).findFirst();
    }

    /**
     * 通用方法，用于在列表中查找符合条件的第一个对象，如果未找到则返回默认值
     *
     * @param list         要搜索的列表
     * @param predicate    判断条件
     * @param defaultValue 默认值
     * @param <T>          列表中对象的类型
     * @return 一个对象，包含符合条件的第一个对象，如果未找到则为defaultValue
     */
    public static <T> T findFirst(List<T> list, Predicate<T> predicate, T defaultValue) {
        return findFirst(list, predicate).orElse(defaultValue);
    }

    /**
     * 通用方法，用于在列表中查找符合条件的第一个对象，如果未找到则返回null
     *
     * @param list      要搜索的列表
     * @param predicate 判断条件
     * @param <T>       列表中对象的类型
     * @return 一个对象，包含符合条件的第一个对象，如果未找到则为null
     */
    public static <T> T findFirstOrNull(List<T> list, Predicate<T> predicate) {
        return findFirst(list, predicate, null);
    }

    /**
     * 通用方法，用于在列表中查找符合条件的对象列表
     *
     * @param list      要搜索的列表
     * @param predicate 判断条件
     * @param <T>       列表中对象的类型
     * @return 对象列表，包含符合条件的对象列表，如果未找到则为空数组
     */
    public static <T> List<T> findList(List<T> list, Predicate<T> predicate) {
        if (CollUtil.isEmpty(list)) {
            return List.of();
        }
        return list.stream().filter(predicate).collect(Collectors.toList());
    }

    /**
     * 通用方法，用于在列表中根据属性值查找符合条件的第一个对象
     *
     * @param list         要搜索的列表
     * @param keyExtractor 获取属性的方法
     * @param key          属性值
     * @param <T>          列表中对象的类型
     * @param <K>          属性的类型
     * @return 一个Optional对象，包含符合条件的第一个对象，如果未找到则为Optional.empty()
     */
    public static <T, K> Optional<T> findFirst(List<T> list, Function<T, K> keyExtractor, K key) {
        return findFirst(list, item -> equals(key, keyExtractor.apply(item)));
    }

    /**
     * 通用方法，用于在列表中根据属性值查找符合条件的第一个对象，如果未找到则返回默认值
     *
     * @param list         要搜索的列表
     * @param keyExtractor 获取属性的方法
     * @param key          属性值
     * @param defaultValue 默认值
     * @param <T>          列表中对象的类型
     * @param <K>          属性的类型
     * @return 一个对象，包含符合条件的第一个对象，如果未找到则为defaultValue
     */
    public static <T, K> T findFirst(List<T> list, Function<T, K> keyExtractor, K key, T defaultValue) {
        return findFirst(list, keyExtractor, key).orElse(defaultValue);
    }

    /**
     * 通用方法，用于在列表中根据属性值查找符合条件的对象列表
     *
     * @param list         要搜索的列表
     * @param keyExtractor 获取属性的方法
     * @param key          属性值
     * @param <T>          列表中对象的类型
     * @param <K>          属性的类型
     * @return 对象列表，包含符合条件的对象列表，如果未找到则为空
     */
    public static <T, K> T findFirstOrNull(List<T> list, Function<T, K> keyExtractor, K key) {
        return findFirst(list, keyExtractor, key, null);
    }

    /**
     * 通用方法，用于在列表中根据属性值查找符合条件的对象列表
     *
     * @param list         要搜索的列表
     * @param keyExtractor 获取属性的方法
     * @param key          属性值
     * @param <T>          列表中对象的类型
     * @param <K>          属性的类型
     * @return 对象列表，包含符合条件的对象列表，如果未找到则为空数组
     */
    public static <T, K> List<T> findList(List<T> list, Function<T, K> keyExtractor, K key) {
        return findList(list, item -> equals(key, keyExtractor.apply(item)));
    }

    /**
     * 比较两个对象是否相等。该方法首先检查传入的参数 a 和 b 是否都为 null，
     * 若任一参数为 null，则直接返回 false，因为 null 与任何值都不相等。
     * <p>
     * 如果两个参数均不为 null，则尝试进行以下操作：
     * <p>
     * 1. 使用{@link ObjUtil}的equals(a, b)方法比较对象 `a` 和 `b` 是否相等。如果相等则返回 true。<br/>
     * 2. 将对象 `a` 和 `b` 分别转换成字符串并比较它们是否相同。这种逻辑通常在处理非直接可比数据类型时采用，
     *    如整型和对应的大写字符型字符串，或者需要基于其他特殊规则判断相等性的情况（例如：特定格式日期的比较）。
     *
     * @param a 待比较的对象1
     * @param b 待比较的对象2
     * @return 如果两个参数都不为 null 或者对象相等或转换后的字符串相同，则返回 true；否则返回 false。
     */
    private static boolean equals(Object a, Object b) {
        // 检查传入的参数是否为 null，若任一参数为 null 则直接返回 false
        if (a == null || b == null) {
            return false;
        }
        // 如果两个对象均不为 null，则尝试直接比较它们
        if (ObjUtil.equals(a, b)) {
            return true;
        }
        // 若直接比较不相等，尝试将它们转换成字符串并比较
        return String.valueOf(a).equals(String.valueOf(b));
    }
}
